import RBush from 'rbush'
import type { LngLat } from '../great-circle'
import { lngLatToSegDist } from '../euclidean'

export interface LineStringIndexOptions<T> {
  data: T[]
  points: (t: T) => LngLat[]
}

interface SegmentItem<T> {
  start: LngLat
  end: LngLat
  data: T
  minX: number
  minY: number
  maxX: number
  maxY: number
}

export class LineStringIndex<T> {
  private idx: RBush<SegmentItem<T>> = new RBush(16)

  constructor(opts: LineStringIndexOptions<T>) {
    const segLst = []
    for (const t of opts.data) {
      segLst.push(...toSegmentItems(t, opts.points(t)))
    }
    this.idx.load(segLst)
  }

  public search(lng: number, lat: number, radius: number): T[] | null {
    const r = this.idx.search({
      minX: lng - radius,
      minY: lat - radius,
      maxX: lng + radius,
      maxY: lng + radius
    })
    if (!r) {
      return null
    }
    const pt = { lng, lat }
    const ret = []
    for (const t of r) {
      const d1 = lngLatToSegDist(pt, t.start, t.end)
      if (d1 <= radius) {
        ret.push(t.data)
      }
    }
    return ret
  }
}

function toSegmentItems<T>(data: T, points: LngLat[]): SegmentItem<T>[] {
  if (points.length === 0) {
    return []
  }
  const ret = new Array<SegmentItem<T>>(points.length - 1)
  for (let i = 0; i < points.length - 1; i++) {
    const start = points[i]
    const end = points[i + 1]
    ret[i] = {
      start,
      end,
      data,
      minX: Math.min(start.lng, end.lng),
      maxX: Math.max(start.lng, end.lng),
      minY: Math.min(start.lat, end.lat),
      maxY: Math.max(start.lat, end.lat)
    }
  }
  return ret
}
